package org.xx.armory.swing.controllers;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.commons.SysUtils;
import org.xx.armory.swing.ResourceLoader;
import org.xx.armory.swing.UIController;
import org.xx.armory.swing.UIControllers;
import org.xx.armory.swing.builders.ActionModel;
import org.xx.armory.swing.builders.BoxLayoutModel;
import org.xx.armory.swing.builders.ButtonModel;
import org.xx.armory.swing.builders.CellModel;
import org.xx.armory.swing.builders.CheckBoxModel;
import org.xx.armory.swing.builders.ComponentModel;
import org.xx.armory.swing.builders.DropDownModel;
import org.xx.armory.swing.builders.GridLayoutModel;
import org.xx.armory.swing.builders.ImageBoxModel;
import org.xx.armory.swing.builders.ImageModel;
import org.xx.armory.swing.builders.LabelModel;
import org.xx.armory.swing.builders.ListBoxModel;
import org.xx.armory.swing.builders.ModelAlignment;
import org.xx.armory.swing.builders.OptionModel;
import org.xx.armory.swing.builders.OptionsModel;
import org.xx.armory.swing.builders.PlaceholderModel;
import org.xx.armory.swing.builders.RowModel;
import org.xx.armory.swing.builders.RulerModel;
import org.xx.armory.swing.builders.SeparatorModel;
import org.xx.armory.swing.builders.SimpleAction;
import org.xx.armory.swing.builders.SplitPaneModel;
import org.xx.armory.swing.builders.TabModel;
import org.xx.armory.swing.builders.TabbedPaneModel;
import org.xx.armory.swing.builders.TableColumnModel;
import org.xx.armory.swing.builders.TableModel;
import org.xx.armory.swing.builders.TextModel;
import org.xx.armory.swing.builders.ToolBarModel;
import org.xx.armory.swing.builders.UIModel;
import org.xx.armory.swing.builders.UIModelClass;
import org.xx.armory.swing.builders.UIModelLoader;
import org.xx.armory.swing.components.BorderType;
import org.xx.armory.swing.components.GBC;
import org.xx.armory.swing.components.ImageBox;
import org.xx.armory.swing.components.InputField;
import org.xx.armory.swing.components.ListItem;
import org.xx.armory.swing.components.Ruler;
import org.xx.armory.swing.components.TypedTableCellRenderer;
import org.xx.armory.swing.components.TypedTableColumn;
import org.xx.armory.swing.components.TypedTableColumnType;
import org.xx.armory.swing.components.TypedTableModel;
import org.xx.armory.swing.components.formatters.CurrencyFormatter;
import org.xx.armory.swing.components.formatters.DateFormatter;
import org.xx.armory.swing.components.formatters.FloatFormatter;
import org.xx.armory.swing.components.formatters.NumberFormatter;
import org.xx.armory.swing.components.formatters.PercentFormatter;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.table.TableColumn;
import javax.swing.text.DefaultCaret;
import javax.swing.text.JTextComponent;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.StyleSheet;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static javax.swing.BorderFactory.createCompoundBorder;
import static javax.swing.BorderFactory.createLineBorder;
import static javax.swing.BorderFactory.createLoweredSoftBevelBorder;
import static javax.swing.BorderFactory.createRaisedSoftBevelBorder;
import static javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.join;
import static org.apache.commons.lang3.StringUtils.repeat;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.BuilderUtils.buildDirectly;
import static org.xx.armory.commons.SysUtils.getOrDefault;
import static org.xx.armory.commons.SysUtils.removeNonDigit;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;
import static org.xx.armory.swing.ComponentUtils.addChangeListener;
import static org.xx.armory.swing.ComponentUtils.combineBorders;
import static org.xx.armory.swing.ComponentUtils.getSelectedItems;
import static org.xx.armory.swing.ComponentUtils.setSelectedItems;
import static org.xx.armory.swing.UIUtils.assertUIThread;
import static org.xx.armory.swing.UIUtils.toBigDecimalObject;
import static org.xx.armory.swing.UIUtils.toBooleanObject;
import static org.xx.armory.swing.UIUtils.toDateObject;
import static org.xx.armory.swing.UIUtils.toDoubleObject;
import static org.xx.armory.swing.UIUtils.toLongObject;
import static org.xx.armory.swing.UIUtils.toStringArray;

public abstract class UIModelController<T extends Container>
        implements UIController {
    private static final Date DEFAULT_DATE = new Date(0);
    private final Logger logger = LoggerFactory.getLogger(UIModelController.class);
    private final T container;
    private final Map<String, String> messages;
    private final Map<String, SimpleAction> actions;
    private final Map<String, Image> images;
    private final Map<String, List<ListItem>> options;
    private final Map<String, Component> components;
    private final Map<JLabel, String> labelsFor;
    protected int margin;

    public UIModelController(
            T container
    ) {
        this.container = notNull(container, "container");
        this.messages = new HashMap<>();
        this.actions = new HashMap<>();
        this.images = new HashMap<>();
        this.options = new HashMap<>();
        this.components = new LinkedHashMap<>();
        this.labelsFor = new HashMap<>();
        this.margin = 0;

        this.container.addPropertyChangeListener(new UIContainerPropertyChangedAdapter());
    }

    /**
     * 判断组件是否根据内容自动调整尺寸。
     * <p>目前只有JLabel是根据内容自动调整尺寸的。</p>
     *
     * @param component
     *         指定的组件。
     * @return 组件是否根据内容自动调整尺寸。
     */
    protected static boolean isAutoSize(
            Component component
    ) {
        return component instanceof JLabel || component instanceof Box.Filler;
    }

    /**
     * 对组件的类型和名称进行格式化，用于调试代码。
     *
     * @param component
     *         指定的组件。
     * @return 格式化后的文本。
     * @throws IllegalArgumentException
     *         如果参数{@code component}是{@code null}。
     */
    protected static String formatComponent(
            Component component
    ) {
        notNull(component, "component");

        final String name = trimToEmpty(component.getName());
        if (name.isEmpty()) {
            return component.getClass().getSimpleName();
        } else {
            return component.getClass().getSimpleName() + "[" + name + "]";
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void load() {
        final Class<? extends Container> containerClass = container.getClass();

        Class<?> uiClass;
        Class<?> superClass = containerClass;
        UIModelClass uiModelClass;
        do {
            uiClass = superClass;
            superClass = uiClass.getSuperclass();
            uiModelClass = uiClass.getAnnotation(UIModelClass.class);
        } while (superClass != null && uiModelClass == null);

        if (uiModelClass == null) {
            // 没找到模型类，那么容器类作为模型类。
            uiClass = containerClass;
        }

        final UIModel uiModel = UIModelLoader.load(uiClass);
        loadFromUIModel(uiModel);

        focusToFirstInput();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void connect(
            String actionName,
            Consumer<ActionEvent> eventHandler
    ) {
        actionName = notBlank(actionName, "name").trim();

        final SimpleAction action = getAction(actionName);
        if (action != null) {
            connect(action, eventHandler);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void call(
            String actionName
    ) {
        actionName = notBlank(actionName, "name").trim();

        final SimpleAction action = getAction(actionName);
        if (action != null) {
            call(action);
        } else {
            throw new IllegalArgumentException("cannot find action[" + actionName + "]");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void connect(
            String name,
            String eventName,
            Consumer<Object> eventHandler
    ) {
        name = trimToEmpty(name);
        eventName = notBlank(eventName, "eventName").trim().toLowerCase();

        Component component;
        if (name.isEmpty()) {
            component = getContainer();
        } else {
            component = get(Component.class, name);
            if (component instanceof JScrollPane) {
                component = ((JScrollPane) component).getViewport().getView();
            }
        }

        connect(component, eventName, eventHandler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void disable(
            String actionName
    ) {
        assertUIThread();

        actionName = notBlank(actionName, "name").trim();

        final SimpleAction action = this.actions.getOrDefault(actionName, null);
        if (action != null) {
            action.setEnabled(false);
        } else {
            throw new IllegalArgumentException("cannot find action[name=" + actionName + "]");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void enable(
            String actionName
    ) {
        assertUIThread();

        actionName = notBlank(actionName, "name").trim();

        final SimpleAction action = this.actions.getOrDefault(actionName, null);
        if (action != null) {
            action.setEnabled(true);
        } else {
            throw new IllegalArgumentException("cannot find action[name=" + actionName + "]");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void show(
            String name
    ) {
        assertUIThread();

        name = notBlank(name, "name").trim();

        get(Component.class, name).setVisible(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void hide(
            String name
    ) {
        assertUIThread();

        name = notBlank(name, "name").trim();

        get(Component.class, name).setVisible(false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void readOnly(
            String name,
            boolean value
    ) {
        assertUIThread();

        name = trimToEmpty(name);
        if (name.isEmpty()) {
            readOnlyAll(value);
            return;
        }

        final Component component = get(Component.class, name);

        if (!readOnlyComponent(component, value)) {
            throw new IllegalArgumentException("cannot set read-only to component: " + component);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final <C extends Component> C get(
            Class<C> clazz,
            String name
    ) {
        name = trimToEmpty(name);

        Component result = this.components.get(name);
        if (result == null) {
            throw new IllegalArgumentException("cannot find component[name=" + name + "]");
        }

        if (clazz.isAssignableFrom(result.getClass())) {
            return clazz.cast(result);
        }

        if (result instanceof JScrollPane) {
            result = ((JScrollPane) result).getViewport().getView();
        }

        return clazz.cast(result);
    }

    public final String getText(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return "";
        } else if (value instanceof String) {
            return (String) value;
        } else if (value instanceof Object[]) {
            return join((Object[]) value, ",");
        } else {
            return value.toString();
        }
    }

    public final void setText(
            String name,
            String text
    ) {
        if (text == null) {
            text = "";
        }

        final JComponent component = get(JComponent.class, name);
        setValue(component, text);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String[] getValues(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return new String[0];
        } else if (value instanceof String) {
            return new String[]{(String) value};
        } else if (value instanceof Object[]) {
            return toStringArray((Object[]) value);
        } else {
            return new String[]{value.toString()};
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setValues(
            String name,
            String[] values
    ) {
        final JComponent component = get(JComponent.class, name);
        setValue(component, values);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int getInteger(
            String name
    ) {
        final Long number = getNumber(name);
        return number != null ? number.intValue() : 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setInteger(
            String name,
            Integer number
    ) {
        setNumber(name, number != null ? number.longValue() : 0L);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Long getNumber(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof String) {
            return toLongObject(removeNonDigit((String) value));
        } else if (value instanceof String[]) {
            return toLongObject(getOrDefault((String[]) value, 0, ""));
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setNumber(
            String name,
            Long number
    ) {
        final JComponent component = get(JComponent.class, name);
        setValue(component, number);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Double getFloat(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            return ((Number) value).doubleValue();
        } else if (value instanceof String) {
            return toDoubleObject(removeNonDigit((String) value));
        } else if (value instanceof String[]) {
            return toDoubleObject(getOrDefault((String[]) value, 0, ""));
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setFloat(
            String name,
            Double value
    ) {
        final JComponent component = get(JComponent.class, name);
        setValue(component, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final BigDecimal getDecimal(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return null;
        } else if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        } else if (value instanceof Number) {
            return new BigDecimal(((Number) value).doubleValue());
        } else if (value instanceof String) {
            return toBigDecimalObject((String) value);
        } else if (value instanceof String[]) {
            return toBigDecimalObject(getOrDefault((String[]) value, 0, ""));
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setDecimal(
            String name,
            BigDecimal value
    ) {
        final JComponent component = get(JComponent.class, name);
        setValue(component, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Date getDate(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return null;
        } else if (value instanceof Date) {
            return (Date) value;
        } else if (value instanceof Number) {
            return new Date(((Number) value).longValue());
        } else if (value instanceof String) {
            return toDateObject(removeNonDigit((String) value));
        } else if (value instanceof String[]) {
            return toDateObject(getOrDefault((String[]) value, 0, ""));
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setDate(
            String name,
            Date date
    ) {
        final JComponent component = get(JComponent.class, name);
        setValue(component, date);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Boolean getBoolean(
            String name
    ) {
        final JComponent component = get(JComponent.class, name);
        final Object value = getValue(component);
        if (value == null) {
            return null;
        } else if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof Number) {
            return ((Number) value).intValue() != 0;
        } else if (value instanceof String) {
            return toBooleanObject((String) value);
        } else if (value instanceof String[]) {
            return toBooleanObject(getOrDefault((String[]) value, 0, ""));
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setBoolean(
            String name,
            Boolean value
    ) {
        final JComponent component = get(JComponent.class, name);
        setValue(component, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String getMessage(
            String name
    ) {
        name = notBlank(name, "name").trim();

        final String f = this.messages.get(name);
        if (f == null) {
            return "${" + name + "}";
        } else if (f.isEmpty()) {
            return "";
        } else {
            return f;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String formatMessage(
            String name,
            Object... values
    ) {
        name = notBlank(name, "name").trim();

        final String f = this.messages.get(name);
        if (f == null) {
            return "${" + name + "}";
        } else if (f.isEmpty()) {
            return "";
        } else {
            return SysUtils.format(f, values);
        }
    }

    /**
     * 执行指定的Action。
     *
     * @param action
     *         Action。
     * @throws IllegalArgumentException
     *         如果参数{@code action}是{@code null}。
     */
    private void call(
            SimpleAction action
    ) {
        notNull(action, "action");
        action.actionPerformed(new ActionEvent(action, 0, ""));
    }

    /**
     * 将Action关联到指定的事件响应程序。
     * <p>如果参数{@code eventHandler}是{@code null}，那么该Action不关联到任何响应程序。</p>
     *
     * @param action
     *         Action。
     * @param eventHandler
     *         事件响应程序。
     * @throws IllegalArgumentException
     *         如果参数{@code action}是{@code null}。
     */
    protected void connect(
            SimpleAction action,
            Consumer<ActionEvent> eventHandler
    ) {
        notNull(action, "action");

        action.setHandler(eventHandler);
    }

    /**
     * 将指定的组件的指定事件关联到指定的事件响应程序。
     * <p>如果参数{@code component}是{@code null}，表示此事件关联到控制器本身。</p>
     * <p>如果参数{@code eventHandler}是{@code null}，那么该组件的该事件不关联到任何响应程序。</p>
     *
     * @param component
     *         组件。
     * @param eventName
     *         组件的事件名字，自动去掉首尾空格。
     * @param eventHandler
     *         事件响应程序。
     * @throws IllegalArgumentException
     *         如果参数{@code eventName}是{@code null}或者只包含空白字符，如果无法关联指定名字的事件。
     */
    protected void connect(
            Component component,
            String eventName,
            Consumer<Object> eventHandler
    ) {
        switch (eventName) {
            case "click":
                connectClick(component, eventHandler);
                break;
            case "change":
                connectChange(component, eventHandler);
                break;
            default:
                throw new IllegalArgumentException("illegal event[name=" + eventName + "]");
        }
    }

    /**
     * 设置所有组件的只读状态。
     * <p>跳过那些无法设置只读状态的组件，不抛出错误。</p>
     *
     * @param value
     *         所有组件的只读状态。
     */
    protected void readOnlyAll(
            boolean value
    ) {
        for (final Map.Entry<String, Component> entry : this.components.entrySet()) {
            final Component component = entry.getValue();
            if (component == null) {
                continue;
            }

            // TODO: 记录原始只读状态，批次允许编辑时，读取原始状态。
            readOnlyComponent(component, value);
        }

        for (final Map.Entry<String, SimpleAction> entry : this.actions.entrySet()) {
            final SimpleAction action = entry.getValue();
            if (action == null) {
                continue;
            }

            // TODO: 记录原始启用状态，批次启用时，读取原始状态。
            action.setEnabled(!value);
        }
    }

    /**
     * 设置指定组件的只读状态。
     *
     * @param component
     *         指定的组件。
     * @param value
     *         只读状态。
     * @return 如果设置成功则返回 {@code true}, 否则返回 {@code false}，如果参数{@code component}是{@code null}则看作设置失败。
     */
    protected boolean readOnlyComponent(
            Component component,
            boolean value
    ) {
        if (component instanceof JScrollPane) {
            component = ((JScrollPane) component).getViewport().getView();
        }

        if (component == null) {
            return false;
        } else if (component instanceof JTextComponent) {
            ((JTextComponent) component).setEditable(!value);

            if (value) {
                // 只读状态下，光标回到开头，并且不跟随文档更新位置。
                ((JTextComponent) component).setCaretPosition(0);
                final DefaultCaret caret = (DefaultCaret) ((JTextComponent) component).getCaret();
                caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);

                // component.setBackground(UIManager.getColor("TextField.disabledBackground"));
                component.setForeground(UIManager.getColor("TextField.inactiveForeground"));
            } else {
                // 可编辑状态下，光标随着文档更新位置。
                final DefaultCaret caret = (DefaultCaret) ((JTextComponent) component).getCaret();
                caret.setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT);

                // component.setBackground(UIManager.getColor("TextField.background"));
                component.setForeground(UIManager.getColor("TextField.foreground"));
            }

            return true;
        } else if (component instanceof JComboBox) {
            component.setEnabled(!value);
            return true;
        } else if (component instanceof AbstractButton) {
            component.setEnabled(!value);
            return true;
        } else if (component instanceof JList) {
            component.setEnabled(!value);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取指定组件的值。
     * <p>如果指定的组件是JScrollPane，那么找到内部的视图组件，并获取视图组件的值。</p>
     *
     * @param component
     *         指定的组件。
     * @return 组件的值。
     * @see JScrollPane#getViewport()
     * @see JViewport#getView()
     */
    protected Object getValue(
            Component component
    ) {
        if (component instanceof JScrollPane) {
            component = ((JScrollPane) component).getViewport().getView();
        }

        if (component instanceof JLabel) {
            return ((JLabel) component).getText();
        } else if (component instanceof JFormattedTextField) {
            return ((JFormattedTextField) component).getValue();
        } else if (component instanceof JTextComponent) {
            return ((JTextComponent) component).getText();
        } else if (component instanceof JToggleButton) {
            return ((JToggleButton) component).isSelected();
        } else if (component instanceof JList) {
            return getSelectedItems(((JList<?>) component));
        } else if (component instanceof JComboBox) {
            return getSelectedItems(((JComboBox<?>) component));
        } else {
            throw new IllegalArgumentException("cannot get value from " + component.getClass());
        }
    }

    /**
     * 设置组件的值。
     *
     * @param component
     *         指定的组件。
     * @param value
     *         组件的值。
     * @see JScrollPane#getViewport()
     * @see JViewport#getView()
     */
    protected void setValue(
            Component component,
            Object value
    ) {
        if (component instanceof JScrollPane) {
            component = ((JScrollPane) component).getViewport().getView();
        }

        if (component instanceof JLabel) {
            ((JLabel) component).setText(value != null ? value.toString() : "");
        } else if (component instanceof JFormattedTextField) {
            ((JFormattedTextField) component).setValue(value);
        } else if (component instanceof JTextComponent) {
            ((JTextComponent) component).setText(value != null ? value.toString() : "");
        } else if (component instanceof JToggleButton) {
            if (value == null) {
                // 不设置任何值。
            } else if (value instanceof Boolean) {
                ((JToggleButton) component).setSelected((Boolean) value);
            } else {
                Boolean b = toBooleanObject(value.toString());
                if (b == null) {
                    return;
                }
                ((JToggleButton) component).setSelected(b);
            }
        } else if (component instanceof JList) {
            if (value instanceof String[]) {
                setSelectedItems((JList<?>) component, (String[]) value);
            } else if (value instanceof Object[]) {
                setSelectedItems((JList<?>) component, toStringArray((Object[]) value));
            } else {
                setSelectedItems((JList<?>) component, split(value.toString(), ","));
            }
        } else if (component instanceof JComboBox) {
            if (value instanceof String[]) {
                setSelectedItems((JComboBox<?>) component, (String[]) value);
            } else if (value instanceof Object[]) {
                setSelectedItems((JComboBox<?>) component, toStringArray((Object[]) value));
            } else {
                setSelectedItems((JComboBox<?>) component, split(value.toString(), ","));
            }
        } else {
            throw new IllegalArgumentException("cannot set value to " + component.getClass());
        }
    }

    protected void connectChange(
            Component component,
            Consumer<Object> eventHandler
    ) {
        if (component instanceof JTextComponent) {
            addChangeListener((JTextComponent) component, eventHandler::accept);
        } else if (component instanceof JToggleButton) {
            ((JToggleButton) component).addChangeListener(eventHandler::accept);
        } else if (component instanceof JComboBox) {
            ((JComboBox<?>) component).addActionListener(eventHandler::accept);
        } else if (component instanceof JTable) {
            ((JTable) component).getSelectionModel().addListSelectionListener(eventHandler::accept);
        } else {
            throw new IllegalArgumentException("illegal event \"change\" of " + component);
        }
    }

    protected void connectClick(
            Component component,
            Consumer<Object> eventHandler
    ) {
        notNull(component, "component");

        component.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(
                    MouseEvent event
            ) {
                super.mouseClicked(event);

                eventHandler.accept(event);
            }
        });
    }

    /**
     * 第一个可以输入的元素获得焦点。
     */
    protected void focusToFirstInput() {
        assertUIThread();

        for (final Component component : this.components.values()) {
            if (component instanceof JTextComponent || component instanceof JComboBox || component instanceof AbstractButton) {
                component.requestFocusInWindow();
                break;
            }
        }
    }

    protected void loadFromUIModel(
            UIModel uiModel
    ) {
        this.margin = uiModel.getMargin();
        createSize(uiModel);
        createMessages(uiModel);
        createActions(uiModel);
        createImages(uiModel);
        createOptions(uiModel);
        createToolBars(uiModel);
        createChildren(uiModel);
        createLabelsFor();
    }

    /**
     * 获取关联的容器。
     *
     * @return 关联的容器。
     */
    protected final T getContainer() {
        return this.container;
    }

    /**
     * 获取指定名字的Action。
     *
     * @param name
     *         Action的名字。
     * @return 指定名字的Action，如果不存在则返回{@code null}。
     */
    protected final SimpleAction getAction(
            String name
    ) {
        return this.actions.getOrDefault(name, null);
    }

    /**
     * 获取指定名字的图像。
     *
     * @param name
     *         图像的名字。
     * @return 指定名字的图像，如果不存在则返回{@code null}。
     */
    protected final Image getImage(
            String name
    ) {
        return this.images.getOrDefault(name, null);
    }

    /**
     * 根据用户界面模型创建界面的整体尺寸。
     *
     * @param uiModel
     *         关联的UI模型。
     */
    protected void createSize(
            UIModel uiModel
    ) {
        int width = uiModel.getWidth();
        int height = uiModel.getHeight();
        if (width > 0 && height > 0) {
            getContainer().setPreferredSize(new Dimension(width, height));
        }
    }

    /**
     * 根据用户界面模型创建消息字面量。
     *
     * @param uiModel
     *         关联的UI模型。
     */
    protected void createMessages(
            UIModel uiModel
    ) {
        this.messages.putAll(uiModel.getMessages());
    }

    /**
     * 根据用户界面模型创建Action。包括{@link UIModel}中定义的Action和预定义的标准Action。
     *
     * @param uiModel
     *         用户界面模型。
     */
    protected void createActions(
            UIModel uiModel
    ) {
        for (final String actionName : uiModel.getStandardActions()) {
            logger.trace("create standard action[name={}]", actionName);
            this.actions.putIfAbsent(actionName,
                                     UIControllers.STANDARD.getAction(actionName));
        }

        for (final ActionModel model : uiModel.getActions()) {
            logger.trace("create action[name={},title={},enabled={},small-icon={}]", model.getName(), model.getTitle(), model.isEnabled(), model.getSmallIcon());
            final Image smallIconImage = loadImage(uiModel.getResourceName(), model.getSmallIcon());
            final SimpleAction action = new SimpleAction(model.getTitle(),
                                                         model.getMnemonic(),
                                                         model.getAccelerator(),
                                                         smallIconImage != null ? new ImageIcon(smallIconImage) : null,
                                                         model.getDescription(),
                                                         model.getCommand(),
                                                         model.isCauseVerification());
            action.setEnabled(model.isEnabled());
            this.actions.putIfAbsent(model.getName(), action);
        }
    }

    /**
     * 从界面模型中加载图片。
     *
     * @param resourceName
     *         加载界面模型的资源名字。
     * @param src
     *         图片文件名。
     * @return 已加载的图片，如果参数{@code src}是{@code null}或者只包含空白字符则返回{@code null}。
     */
    protected Image loadImage(
            String resourceName,
            String src
    ) {
        src = trimToEmpty(src);

        if (src.isEmpty()) {
            return null;
        }

        return ResourceLoader.getInstance().loadImage(resourceName, src);
    }

    /**
     * 根据用户界面模型创建图像。
     *
     * @param uiModel
     *         用户界面模型。
     */
    protected void createImages(
            UIModel uiModel
    ) {
        for (final ImageModel model : uiModel.getImages()) {
            final Image image = loadImage(uiModel.getResourceName(), model.getSrc());
            if (image == null) {
                throw new IllegalArgumentException("cannot find image: " + model.getSrc());
            }

            this.images.putIfAbsent(model.getName(), image);
        }
    }

    protected void createOptions(
            UIModel uiModel
    ) {
        for (final OptionsModel model : uiModel.getOptions()) {
            this.options.putIfAbsent(model.getName(), model.getChildren()
                                                           .stream()
                                                           .map(optionModel -> new ListItem(optionModel.getText(), optionModel.getValue()))
                                                           .collect(Collectors.toList()));
        }
    }

    /**
     * 根据用户界面模型创建工具栏。
     *
     * @param uiModel
     *         用户界面模型。
     */
    protected void createToolBars(
            UIModel uiModel
    ) {
        final List<ToolBarModel> toolBars = uiModel.getToolBars();
        if (toolBars.isEmpty()) {
            return;
        }

        for (final ToolBarModel model : toolBars) {
            final JToolBar toolBar = new JToolBar();
            for (final String item : model.getChildren()) {
                if ("-".equals(item)) {
                    // 分隔符
                    toolBar.addSeparator();
                } else {
                    // Action
                    final Action action = this.getAction(item);
                    if (action == null) {
                        logger.warn("cannot find action: {}", item);
                    } else {
                        toolBar.add(action);
                    }
                }
            }
            getContainer().add(toolBar, BorderLayout.NORTH);
        }
    }

    /**
     * 创建组件和布局。
     *
     * @param uiModel
     *         UI模型。
     */
    protected void createChildren(
            UIModel uiModel
    ) {
        final List<ComponentModel> children = uiModel.getComponents();
        if (children.isEmpty()) {
            return;
        }
        if (children.size() == 1) {
            final ComponentModel child = children.get(0);
            if (child instanceof BoxLayoutModel) {
                createBoxLayout(this.getContainer(), (BoxLayoutModel) child);
                return;
            } else if (child instanceof GridLayoutModel) {
                createGridLayout(this.getContainer(), (GridLayoutModel) child);
                return;
            }
        }

        final BoxLayout mainLayout = new BoxLayout(getContainer(), BoxLayout.Y_AXIS);
        this.getContainer().setLayout(mainLayout);

        addChildrenToContainer(getContainer(), children);

        adjustBoxChildrenSize(getContainer(), false);
    }

    /**
     * 根据边框UI模型为指定的组件创建边框。
     *
     * @param component
     *         需要创建边框的组件。
     * @param title
     *         组件的标题。
     * @param borderType
     *         组件的边框UI模型。
     */
    protected void createBorder(
            JComponent component,
            String title,
            BorderType borderType
    ) {
        notNull(component, "component");
        notNull(borderType, "borderType");

        Border outsideBorder;
        Border insideBorder;
        if (!isBlank(title)) {
            outsideBorder = BorderFactory.createTitledBorder(title);
        } else {
            outsideBorder = null;
        }

        if (borderType != BorderType.NONE) {
            insideBorder = BorderFactory.createEmptyBorder(this.margin, this.margin, this.margin, this.margin);
            switch (borderType) {
                case SOLID:
                    insideBorder = createCompoundBorder(createLineBorder(component.getForeground()), insideBorder);
                    break;
                case LOWERED:
                    insideBorder = createCompoundBorder(createLoweredSoftBevelBorder(), insideBorder);
                    break;
                case RAISED:
                    insideBorder = createCompoundBorder(createRaisedSoftBevelBorder(), insideBorder);
                    break;
            }
        } else {
            insideBorder = null;
        }

        insideBorder = combineBorders(insideBorder, component.getBorder());

        component.setBorder(combineBorders(outsideBorder, insideBorder));
    }

    /**
     * 创建Box布局的组件。
     *
     * @param model
     *         Box布局的组件的UI模型。
     * @return Box布局的组件。
     * @throws IllegalArgumentException
     *         如果参数{@code model}是{@code null}。
     */
    protected final JPanel createBox(
            BoxLayoutModel model
    ) {
        notNull(model, "model");

        final JPanel result = new JPanel();

        createBorder(result, model.getTitle(), model.getBorder());

        createBoxLayout(result, model);

        final Dimension preferredSize = result.getPreferredSize();
        result.setMinimumSize(preferredSize);

        if (model.getAxis() == BoxLayout.X_AXIS) {
            // 允许水平自动扩展，垂直的高度固定。
            preferredSize.width = Integer.MAX_VALUE;
        } else {
            // 允许垂直自动扩展，水平的宽度固定。
            preferredSize.height = Integer.MAX_VALUE;
        }
        result.setMaximumSize(preferredSize);

        componentCreated(model, result);

        return result;
    }

    /**
     * 创建Grid布局的组件。
     *
     * @param model
     *         Grid布局的组件的UI模型。
     * @return Grid布局的组件。
     * @throws IllegalArgumentException
     *         如果参数{@code model}是{@code null}。
     */
    protected final JPanel createGrid(
            GridLayoutModel model
    ) {
        notNull(model, "model");

        final JPanel result = new JPanel();

        createBorder(result, model.getTitle(), model.getBorder());

        createGridLayout(result, model);
        result.updateUI();
        result.setMinimumSize(result.getPreferredSize());

        componentCreated(model, result);

        return result;
    }

    protected final void createBoxLayout(
            Container container,
            BoxLayoutModel model
    ) {
        notNull(container, "container");
        notNull(model, "model");

        @SuppressWarnings("MagicConstant") final BoxLayout layout = new BoxLayout(container, model.getAxis());

        container.setLayout(layout);

        final ModelAlignment alignment = model.getHorizontalAlignment();
        if (alignment == ModelAlignment.CENTER || alignment == ModelAlignment.LAST) {
            // 如果Box居中对齐或者右对齐，那么在Box的左侧加入spring。
            container.add(Box.createGlue());
        }
        addChildrenToContainer(container, model.getChildren());
        if (alignment == ModelAlignment.CENTER || alignment == ModelAlignment.FIRST) {
            // 如果Box居中对齐或者左对齐，那么在Box的右侧加入spring。
            container.add(Box.createGlue());
        }

        adjustBoxChildrenSize(container, alignment == ModelAlignment.BOTH);
    }

    protected final void createGridLayout(
            Container container,
            GridLayoutModel model
    ) {
        notNull(container, "container");
        notNull(model, "model");

        final GridBagLayout layout = new GridBagLayout();

        container.setLayout(layout);

        for (final RowModel row : model.getRows()) {
            for (final CellModel cell : row.getCells()) {
                createGridCell(container, cell);
            }
        }
    }

    /**
     * 创建单元格。
     * <p>创建单元格实际上是创建该单元格内的组件，然后按照单元格的布局将该组件加入到表格布局中。</p>
     * <ul>
     * <li>首先判断单元格模型是否存在需要创建的组件，如果不存在则返回。</li>
     * <li>然后根据单元格模型创建单元格约束。</li>
     * <li>然后根据单元格模型内的组件模型创建组件。</li>
     * <li>将已创建的组件和单元格约束加入到表格布局中。</li>
     * </ul>
     *
     * @param target
     *         目标组件容器。
     * @param model
     *         单元格模型。
     * @see CellModel#getChild()
     */
    protected final void createGridCell(
            Container target,
            CellModel model
    ) {
        final ComponentModel childModel = model.getChild();
        if (childModel == null) {
            return;
        }

        final GridBagConstraints constraints = createGBC(model);
        final Component child = createComponent(childModel);

        if (child != null) {
            logger.trace("add component {} to grid {}", formatComponent(child), constraints);
            target.add(child, constraints);
        }
    }

    /**
     * 创建GridBagLayout布局所需要的约束。
     *
     * @param cell
     *         单元格模型。
     * @return 布局约束。
     */
    protected final GridBagConstraints createGBC(
            CellModel cell
    ) {
        final GBC result = new GBC(cell.getColumnNo(), cell.getRowNo(), cell.getColumnSpan(), cell.getRowSpan())
                .setWeightX(cell.getHorizontalStretch())
                .setWeightY(cell.getVerticalStretch())
                .setMargin(cell.getMargin());

        // 根据对齐方式来设置锚点。
        final ModelAlignment hAlignment = cell.getHorizontalAlignment();
        final ModelAlignment vAlignment = cell.getVerticalAlignment();

        if (hAlignment == ModelAlignment.NONE) {
            throw new IllegalArgumentException("illegal horizontal alignment: x = " + result.gridx + ", y = " + result.gridy + ", " + hAlignment);
        }
        if (vAlignment == ModelAlignment.NONE) {
            throw new IllegalArgumentException("illegal vertical alignment: x = " + result.gridx + ", y = " + result.gridy + ", " + vAlignment);
        }

        // 设置对齐方式。
        result.setAnchor(makeAnchor(hAlignment, vAlignment));

        // 设置拉伸。
        if (hAlignment == ModelAlignment.BOTH && vAlignment == ModelAlignment.BOTH) {
            // 水平和垂直方向上都拉伸。
            result.setFill(GridBagConstraints.BOTH);
        } else if (hAlignment == ModelAlignment.BOTH) {
            // 仅水平拉伸。
            result.setFill(GridBagConstraints.HORIZONTAL);
        } else if (vAlignment == ModelAlignment.BOTH) {
            // 仅垂直拉伸。
            result.setFill(GridBagConstraints.VERTICAL);
        }

        return result;
    }

    /**
     * 通过水平和垂直对齐方式计算锚点。
     *
     * @param horizontalAlignment
     *         水平对齐方式。
     * @param verticalAlignment
     *         垂直对齐方式。
     * @return 锚点。
     */
    protected int makeAnchor(
            ModelAlignment horizontalAlignment,
            ModelAlignment verticalAlignment
    ) {
        if (horizontalAlignment == ModelAlignment.FIRST && verticalAlignment == ModelAlignment.FIRST) {
            return GridBagConstraints.NORTHWEST;
        } else if (horizontalAlignment == ModelAlignment.FIRST && isCenterOrBoth(verticalAlignment)) {
            return GridBagConstraints.WEST;
        } else if (horizontalAlignment == ModelAlignment.FIRST && verticalAlignment == ModelAlignment.LAST) {
            return GridBagConstraints.SOUTHWEST;
        } else if (isCenterOrBoth(horizontalAlignment) && verticalAlignment == ModelAlignment.FIRST) {
            return GridBagConstraints.NORTH;
        } else if (isCenterOrBoth(horizontalAlignment) && isCenterOrBoth(verticalAlignment)) {
            return GridBagConstraints.CENTER;
        } else if (isCenterOrBoth(horizontalAlignment) && verticalAlignment == ModelAlignment.LAST) {
            return GridBagConstraints.SOUTH;
        } else if (horizontalAlignment == ModelAlignment.LAST && verticalAlignment == ModelAlignment.FIRST) {
            return GridBagConstraints.NORTHEAST;
        } else if (horizontalAlignment == ModelAlignment.LAST && isCenterOrBoth(verticalAlignment)) {
            return GridBagConstraints.EAST;
        } else if (horizontalAlignment == ModelAlignment.LAST && verticalAlignment == ModelAlignment.LAST) {
            return GridBagConstraints.SOUTHEAST;
        } else {
            throw new IllegalArgumentException("illegal alignments: " + horizontalAlignment + ", " + verticalAlignment);
        }
    }

    /**
     * 判断指定的对齐方式是否是居中对齐或者两端对齐。
     * <p>从对称性的角度，居中对齐和两端对齐都是对称的。</p>
     *
     * @param alignment
     *         指定的对齐方式。
     * @return 是否居中对齐或者两端对齐。
     */
    protected boolean isCenterOrBoth(
            ModelAlignment alignment
    ) {
        return alignment == ModelAlignment.CENTER || alignment == ModelAlignment.BOTH;
    }

    /**
     * 将组件添加到容器中。
     *
     * @param container
     *         目标容器。
     * @param models
     *         用于创建组件的UI模型。
     * @throws IllegalArgumentException
     *         如果参数{@code container}或者{@code models}是{@code null}。
     */
    protected final void addChildrenToContainer(
            Container container,
            Collection<ComponentModel> models
    ) {
        notNull(container, "container");
        notNull(models, "models");

        for (final ComponentModel model : models) {
            if (model == null) {
                continue;
            }

            final Component component = createComponent(model);

            logger.trace("add component {} to container {}", formatComponent(component), formatComponent(container));

            container.add(component);
        }
    }

    /**
     * 在Box布局中调整组件的尺寸。
     *
     * @param container
     *         采用Box布局的容器。
     * @param bothAlignment
     *         组件是否需要在容器中两端对齐。
     * @throws IllegalArgumentException
     *         如果参数{@code container}是{@code null}，或者此容器没有采用Box布局。
     */
    protected void adjustBoxChildrenSize(
            Container container,
            boolean bothAlignment
    ) {
        notNull(container, "container");

        LayoutManager layout = container.getLayout();
        if (!(layout instanceof BoxLayout)) {
            throw new IllegalArgumentException("illegal layout: " + layout + ", expected BoxLayout");
        }

        switch (((BoxLayout) layout).getAxis()) {
            case BoxLayout.X_AXIS:
            case BoxLayout.LINE_AXIS:
                for (final Component component : container.getComponents()) {
                    if (!isAutoSize(component)) {
                        // 如果组件不能根据内容自动调整尺寸，那么设置最大尺寸，允许Box布局通过最大尺寸来调整各个组件的尺寸。
                        final Dimension preferredSize = component.getPreferredSize();
                        // 高度方向上允许自由扩展。
                        preferredSize.height = Integer.MAX_VALUE;
                        if (bothAlignment) {
                            // 如果此容器是两端对齐，那么将组件的最大尺寸设置为无穷大，允许自由扩展。
                            preferredSize.width = Integer.MAX_VALUE;
                        }
                        component.setMaximumSize(preferredSize);
                    }
                }
                break;
            case BoxLayout.Y_AXIS:
            case BoxLayout.PAGE_AXIS:
                for (final Component component : container.getComponents()) {
                    if (!isAutoSize(component)) {
                        // 如果组件不能根据内容自动调整尺寸，那么设置最大尺寸，允许Box布局通过最大尺寸来调整各个组件的尺寸。
                        final Dimension preferredSize = component.getPreferredSize();
                        // 宽度方向上允许自由扩展。
                        preferredSize.width = Integer.MAX_VALUE;
                        if (bothAlignment) {
                            // 如果此容器是两端对齐，那么将组件的最大尺寸设置为无穷大，允许自由扩展。
                            preferredSize.height = Integer.MAX_VALUE;
                        }
                        component.setMaximumSize(preferredSize);
                    }
                }
                break;
        }
    }

    /**
     * 设置容器的内部对齐方式。
     *
     * @param container
     *         容器。
     * @param horizontalAlignment
     *         水平对齐方式，如果未指定则居中对齐。
     * @param verticalAlignment
     *         垂直对齐方式，如果未指定则居中对齐。
     */
    protected final void setContainerAlignment(
            JComponent container,
            ModelAlignment horizontalAlignment,
            ModelAlignment verticalAlignment
    ) {
        final float alignmentX;
        final float alignmentY;
        switch (horizontalAlignment) {
            case FIRST:
                alignmentX = Box.LEFT_ALIGNMENT;
                break;
            case CENTER:
                alignmentX = Box.CENTER_ALIGNMENT;
                break;
            case LAST:
                alignmentX = Box.RIGHT_ALIGNMENT;
                break;
            default:
                alignmentX = Box.CENTER_ALIGNMENT;
        }
        switch (verticalAlignment) {
            case FIRST:
                alignmentY = Box.TOP_ALIGNMENT;
                break;
            case CENTER:
                alignmentY = Box.CENTER_ALIGNMENT;
                break;
            case LAST:
                alignmentY = Box.BOTTOM_ALIGNMENT;
                break;
            default:
                alignmentY = Box.CENTER_ALIGNMENT;
        }

        for (final Component child : container.getComponents()) {
            if (child instanceof JComponent) {
                ((JComponent) child).setAlignmentX(alignmentX);
                ((JComponent) child).setAlignmentY(alignmentY);
            }
        }
    }

    /**
     * 根据模型创建组件。
     *
     * @param model
     *         组件模型。
     * @return 组件。
     * @throws IllegalArgumentException
     *         如果参数{@code model}是{@code null}或者无法通过参数{@code model}创建组件。
     */
    protected final Component createComponent(
            ComponentModel model
    ) {
        notNull(model, "model");

        if (model instanceof LabelModel) {
            if (((LabelModel) model).isHtml()) {
                return createHtml((LabelModel) model);
            } else {
                return createLabel((LabelModel) model);
            }
        } else if (model instanceof ImageBoxModel) {
            return createImageBox((ImageBoxModel) model);
        } else if (model instanceof org.xx.armory.swing.builders.ButtonModel) {
            return createButton((org.xx.armory.swing.builders.ButtonModel) model);
        } else if (model instanceof TextModel) {
            return createTextField((TextModel) model);
        } else if (model instanceof CheckBoxModel) {
            return createCheckBox((CheckBoxModel) model);
        } else if (model instanceof DropDownModel) {
            return createDropDown((DropDownModel) model);
        } else if (model instanceof ListBoxModel) {
            return createListBox((ListBoxModel) model);
        } else if (model instanceof TableModel) {
            return createTable((TableModel) model);
        } else if (model instanceof TabbedPaneModel) {
            return createTabbedPane((TabbedPaneModel) model);
        } else if (model instanceof SplitPaneModel) {
            return createSplitPane((SplitPaneModel) model);
        } else if (model instanceof SeparatorModel) {
            return createSeparator((SeparatorModel) model);
        } else if (model instanceof RulerModel) {
            return createRuler((RulerModel) model);
        } else if (model instanceof PlaceholderModel) {
            return createPlaceholder((PlaceholderModel) model);
        } else if (model instanceof BoxLayoutModel) {
            return createBox((BoxLayoutModel) model);
        } else if (model instanceof GridLayoutModel) {
            return createGrid((GridLayoutModel) model);
        } else {
            throw new IllegalArgumentException("unknown component: " + model);
        }
    }

    protected final JTextPane createHtml(
            LabelModel model
    ) {
        final JTextPane result = new JTextPane();
        result.setText(model.getText());
        result.setContentType("text/html");
        result.setEditable(false);
        result.setBackground(getContainer().getBackground());

        final HTMLDocument document = (HTMLDocument) result.getStyledDocument();
        final StyleSheet docStyleSheet = document.getStyleSheet();
        docStyleSheet.addRule("html { margin: 0; padding: 0}");
        docStyleSheet.addRule("body { margin: 0; padding: 0}");
        final int lineMargin = (int) (getContainer().getFontMetrics(getContainer().getFont()).getHeight() * .2);
        docStyleSheet.addRule("p { margin-top: " + lineMargin + "px; margin-bottom: " + lineMargin + "px; padding: 0 }");

        componentCreated(model, result);

        return result;
    }

    private void componentCreated(
            ComponentModel model,
            Component result
    ) {
        if (!model.getName().isEmpty()) {
            result.setName(model.getName());
            this.components.put(model.getName(), result);
        }

        result.setVisible(model.isVisible());
    }

    protected final JLabel createLabel(
            LabelModel model
    ) {
        final JLabel result = new JLabel();

        result.setText(model.getText());

        if (model.getMnemonic() != '\0') {
            result.setDisplayedMnemonic(model.getMnemonic());
        }

        if (!model.getForName().isEmpty()) {
            this.labelsFor.put(result, model.getForName());
        }

        switch (model.getAlignment()) {
            case NONE:
                break;
            case FIRST:
                result.setHorizontalAlignment(JLabel.LEADING);
                break;
            case LAST:
                result.setHorizontalAlignment(JLabel.TRAILING);
                break;
            case CENTER:
                result.setHorizontalAlignment(JLabel.CENTER);
                break;
            case BOTH:
                throw new IllegalArgumentException("illegal alignment: " + model.getAlignment());
        }

        componentCreated(model, result);

        return result;
    }

    protected final ImageBox createImageBox(
            ImageBoxModel model
    ) {
        final ImageBox result = new ImageBox();

        final String imageName = model.getImageName();
        final String src = model.getSrc();
        if (!imageName.isEmpty()) {
            final Image data = getImage(imageName);
            if (data != null) {
                result.setData(data);
            } else {
                throw new IllegalArgumentException("cannot find image: " + imageName);
            }
        } else if (!src.isEmpty()) {
            final Image data = loadImage("", src);
            if (data != null) {
                result.setData(data);
            }
        }

        componentCreated(model, result);

        return result;
    }

    protected final JTabbedPane createTabbedPane(
            TabbedPaneModel model
    ) {
        final JTabbedPane result = new JTabbedPane();

        for (final TabModel tab : model.getTabs()) {
            result.addTab(tab.getTitle(), null, createComponent(tab.getChild()), tab.getDescription());
        }

        componentCreated(model, result);

        return result;
    }

    protected final JSplitPane createSplitPane(
            SplitPaneModel model
    ) {
        final JSplitPane result;

        final Component firstChild = createComponent(model.getFirst());
        final Component lastChild = createComponent(model.getLast());

        switch (model.getAxis()) {
            case BoxLayout.X_AXIS:
                result = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, firstChild, lastChild);
                break;
            case BoxLayout.Y_AXIS:
                result = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false, firstChild, lastChild);
                break;
            default:
                throw new IllegalArgumentException("illegal axis: " + model.getAxis());
        }

        // 自动调整分隔条的默认位置。
        final Dimension size = result.getPreferredSize();
        final Dimension firstSize = firstChild.getPreferredSize();
        final Dimension lastSize = firstChild.getPreferredSize();
        int newLocation;
        switch (model.getDividerLocation()) {
            case SplitPaneModel.DIVIDER_FIRST:
                // 第一个组件按最合适尺寸显示。
                newLocation = model.getAxis() == BoxLayout.X_AXIS ? firstSize.width : firstSize.height;
                break;
            case SplitPaneModel.DIVIDER_CENTER:
                // 两个组件平分显示区域。
                newLocation = model.getAxis() == BoxLayout.X_AXIS ? size.width / 2 : size.height / 2;
                break;
            case SplitPaneModel.DIVIDER_LAST:
                // 第二个组件按最合适尺寸显示。
                newLocation = model.getAxis() == BoxLayout.X_AXIS ? size.width - lastSize.width : size.height - lastSize.height;
                break;
            default:
                newLocation = 0;
        }
        newLocation -= result.getDividerSize();
        if (newLocation > 0) {
            result.setDividerLocation(newLocation);
        } else {
            result.setDividerLocation(0.5D);
        }

        componentCreated(model, result);

        return result;
    }

    protected final JComponent createTextField(
            TextModel model
    ) {
        switch (model.getType()) {
            case PASSWORD:
                return createPassword(model);
            case TEXT_AREA:
                return createTextArea(model);
            case NUMBER:
                return createNumber(model);
            case FLOAT:
                return createFloat(model);
            case PERCENT:
                return createPercent(model);
            case CURRENCY:
                return createCurrency(model);
            case DATE:
                return createDate(model);
            case DATE_TIME:
                return createDateTime(model);
            default:
                if (model.hasOptions()) {
                    return createComboBox(model);
                } else {
                    return createSimpleText(model);
                }
        }
    }

    protected JTextField createTextField(
            String prefix,
            String suffix,
            int size,
            JFormattedTextField.AbstractFormatter formatter
    ) {
        final JTextField result;
        if (!prefix.isEmpty() || !suffix.isEmpty()) {
            result = new InputField(prefix, suffix, formatter);
            ((JFormattedTextField) result).setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
        } else if (formatter != null) {
            result = new JFormattedTextField(formatter);
            ((JFormattedTextField) result).setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
        } else {
            result = new JTextField();
        }

        result.setColumns(size);

        return result;
    }

    protected JTextField createSimpleText(
            TextModel model
    ) {
        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), null);

        result.setHorizontalAlignment(JTextField.LEADING);

        componentCreated(model, result);

        return result;
    }

    protected JTextField createNumber(
            TextModel model
    ) {
        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), new NumberFormatter());

        result.setHorizontalAlignment(JTextField.TRAILING);

        componentCreated(model, result);

        return result;
    }

    protected JTextField createFloat(
            TextModel model
    ) {
        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), new FloatFormatter());

        result.setHorizontalAlignment(JTextField.TRAILING);

        componentCreated(model, result);

        return result;
    }

    protected JTextField createPercent(
            TextModel model
    ) {
        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), new PercentFormatter());

        result.setHorizontalAlignment(JTextField.TRAILING);

        componentCreated(model, result);

        return result;
    }

    protected JTextField createCurrency(
            TextModel model
    ) {
        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), new CurrencyFormatter());

        result.setHorizontalAlignment(JTextField.TRAILING);

        componentCreated(model, result);

        return result;
    }

    protected JTextField createDate(
            TextModel model
    ) {
        final DateFormatter dateFormatter = new DateFormatter();
        final String defaultDateText = dateFormatter.valueToString(DEFAULT_DATE);

        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), dateFormatter);
        result.setHorizontalAlignment(JTextField.LEADING);
        result.updateUI();

        result.setColumns(defaultDateText.length());
        final int textWidth = SwingUtilities.computeStringWidth(result.getFontMetrics(result.getFont()), defaultDateText + "M");
        final int width = textWidth + result.getInsets().left + result.getInsets().right;
        final int height = result.getPreferredSize().height;

        result.setColumns(0);
        final Dimension size = new Dimension(width, height);
        result.setPreferredSize(size);
        result.setMinimumSize(size);

        componentCreated(model, result);

        return result;
    }

    protected JTextField createDateTime(
            TextModel model
    ) {
        final DateFormatter dateFormatter = new DateFormatter();
        final String defaultDateText = dateFormatter.valueToString(DEFAULT_DATE);

        final JTextField result = createTextField(model.getPrefix(), model.getSuffix(), model.getSize(), dateFormatter);
        result.setHorizontalAlignment(JTextField.LEADING);
        result.updateUI();

        result.setColumns(defaultDateText.length());
        final int textWidth = SwingUtilities.computeStringWidth(result.getFontMetrics(result.getFont()), defaultDateText + "M");
        final int width = textWidth + result.getInsets().left + result.getInsets().right;
        final int height = result.getPreferredSize().height;

        result.setColumns(0);
        final Dimension size = new Dimension(width, height);
        result.setPreferredSize(size);
        result.setMinimumSize(size);

        componentCreated(model, result);

        return result;
    }

    protected final JScrollPane createTextArea(
            TextModel model
    ) {
        final JTextArea result = new JTextArea();

        // 禁止显示水平滚动条，永远显示垂直滚动条。
        // 允许自动换行，允许在字符边界处换行（可能截断长单词）。
        result.setColumns(model.getSize());
        result.setRows(model.getRowSize());
        result.setWrapStyleWord(false);
        result.setLineWrap(true);
        result.setTabSize(4);

        final JScrollPane scrollPane = new JScrollPane(result);

        scrollPane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
        componentCreated(model, scrollPane);

        return scrollPane;
    }

    protected JPasswordField createPassword(
            TextModel model
    ) {
        final JPasswordField result = new JPasswordField();

        result.setColumns(model.getSize());

        componentCreated(model, result);

        return result;
    }

    protected JCheckBox createCheckBox(
            CheckBoxModel model
    ) {
        final JCheckBox result = new JCheckBox(model.getText());

        result.setBorderPaintedFlat(true);

        componentCreated(model, result);

        return result;
    }

    protected JButton createButton(
            ButtonModel model
    ) {
        final JButton result = new JButton();

        result.setAction(getAction(model.getActionName()));
        result.setHorizontalTextPosition(JButton.RIGHT); // 文字靠右。
        final int buttonVerticalMargin = (int) (this.margin * 1.5);
        final int buttonHorizontalMargin;
        if (result.getIcon() != null) {
            buttonHorizontalMargin = (int) (buttonVerticalMargin * 2.4); // 水平间距是垂直间距的2.4倍。
        } else {
            buttonHorizontalMargin = (int) (buttonVerticalMargin * 2.8);
        }
        result.setBorder(BorderFactory.createEmptyBorder(buttonVerticalMargin,
                                                         // 如果包含图标，那么左侧边距(即图标侧)减少若干像素。
                                                         result.getIcon() != null ? buttonHorizontalMargin - 2 : buttonHorizontalMargin,
                                                         buttonVerticalMargin, buttonHorizontalMargin));

        componentCreated(model, result);

        return result;
    }

    protected JComboBox<String> createComboBox(
            TextModel model
    ) {
        // 构造数据模型。
        final javax.swing.DefaultComboBoxModel<String> comboBoxModel = new DefaultComboBoxModel<>();

        final String optionsName = model.getOptionsName();
        if (!optionsName.isEmpty()) {
            // 指定了选项组。
            final List<ListItem> options = this.options.get(optionsName);
            if (options == null) {
                throw new IllegalArgumentException("cannot find options: " + optionsName);
            }

            for (final ListItem option : options) {
                final String value = option.getValue();
                comboBoxModel.addElement(!value.isEmpty() ? value : option.getText());
            }
        }

        for (final OptionModel optionModel : model.getOptions()) {
            final String value = optionModel.getValue();
            comboBoxModel.addElement(!value.isEmpty() ? value : optionModel.getText());
        }

        // 构造组件。
        final JComboBox<String> result = new JComboBox<>(comboBoxModel);
        result.setEditable(true);

        // 调整编辑区域的尺寸。
        final JTextField editor = (JTextField) result.getEditor().getEditorComponent();
        editor.setColumns(model.getSize());

        result.setMaximumSize(result.getPreferredSize());

        componentCreated(model, result);

        return result;
    }

    protected JComboBox<?> createDropDown(
            DropDownModel model
    ) {
        // 构造数据模型。
        final javax.swing.DefaultComboBoxModel<ListItem> comboBoxModel = new DefaultComboBoxModel<>();
        if (model.isIncludeAll()) {
            comboBoxModel.addElement(new ListItem(
                    UIControllers.STANDARD.getMessage("item-all"), Integer.MAX_VALUE));
        }
        if (model.isIncludeNone()) {
            comboBoxModel.addElement(new ListItem(
                    UIControllers.STANDARD.getMessage("item-none"), 0));
        }

        final String optionsName = model.getOptionsName();
        if (!optionsName.isEmpty()) {
            // 指定了选项组。
            final List<ListItem> options = this.options.get(optionsName);
            if (options == null) {
                throw new IllegalArgumentException("cannot find options: " + optionsName);
            }

            for (final ListItem option : options) {
                comboBoxModel.addElement(option);
            }
        }

        for (final OptionModel optionModel : model.getOptions()) {
            comboBoxModel.addElement(new ListItem(optionModel.getText(), optionModel.getValue()));
        }

        // 构造组件。
        final JComboBox<ListItem> result = new JComboBox<>(comboBoxModel);
        result.setEditable(false);

        if (model.getSize() > 0) {
            // 调整显示区域尺寸。
            result.setPrototypeDisplayValue(new ListItem(repeat('m', model.getSize()), 0));
        }

        result.setMaximumSize(result.getPreferredSize());

        componentCreated(model, result);

        return result;
    }

    protected JScrollPane createListBox(
            ListBoxModel model
    ) {
        JList<ListItem> result = new JList<>();

        if (model.getSize() > 0) {
            result.setPrototypeCellValue(new ListItem(repeat('m', model.getSize()), 0));
        }

        // 以单列显示方式计算最合适尺寸。
        // 然后宽度加倍作为最合适的视口尺寸。
        result.setVisibleRowCount(model.getRowSize());
        result.setLayoutOrientation(JList.VERTICAL);
        final Dimension preferredSize = result.getPreferredScrollableViewportSize();
        preferredSize.width *= 2;

        result.setLayoutOrientation(JList.VERTICAL_WRAP);
        result.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

        final JScrollPane scrollPane = new JScrollPane(result);
        scrollPane.getViewport().setPreferredSize(preferredSize);

        componentCreated(model, scrollPane);

        return scrollPane;
    }

    protected final JScrollPane createTable(
            TableModel model
    ) {
        // 构造表格数据模型。
        final TypedTableModel tableModel = new TypedTableModel(
                model.getColumns().stream()
                     .map(columnModel -> new TypedTableColumn(
                             columnModel.getName(),
                             columnModel.getTitle(),
                             columnModel.getType(),
                             columnModel.getSize()
                     ))
                     .toArray(TypedTableColumn[]::new));

        // 为表格模型中的列添加查找表。
        for (final TableColumnModel columnModel : model.getColumns()) {
            final String optionsName = columnModel.getOptionsName();
            final List<OptionModel> optionModels = columnModel.getOptions();
            final Map<String, String> lookUpTable = new HashMap<>();

            if (!optionsName.isEmpty()) {
                // 指定了选项组。
                final List<ListItem> options = this.options.get(optionsName);
                if (options == null) {
                    throw new IllegalArgumentException("cannot find options: " + optionsName);
                }

                options.forEach(item -> lookUpTable.put(item.getValue(), item.getText()));
            }

            if (!optionModels.isEmpty()) {
                // 指定了选项。
                optionModels.forEach(optionModel -> lookUpTable.put(optionModel.getValue(), optionModel.getText()));
            }

            if (!lookUpTable.isEmpty()) {
                tableModel.setLookUpTable(columnModel.getName(), lookUpTable);
            }
        }

        final JTable result = new JTable(tableModel);
        result.setCellSelectionEnabled(true);
        result.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        result.setColumnSelectionAllowed(true);
        result.setRowSelectionAllowed(true);
        result.setSelectionMode(MULTIPLE_INTERVAL_SELECTION);
        result.setFillsViewportHeight(true);
        result.setAutoCreateRowSorter(true);
        result.setRowHeight(result.getRowHeight() + (int) (this.margin * 1.5));

        int columnIndex = 0;
        for (final TypedTableColumn column : tableModel.getAllColumns()) {
            final TableColumn tableColumn = result.getColumnModel().getColumn(columnIndex);
            if (column.getType() != TypedTableColumnType.CHECK_BOX) {
                tableColumn.setCellRenderer(new TypedTableCellRenderer(column.getType()));
            } else {
                final JCheckBox checkBox = new JCheckBox();
                checkBox.setText("");
                checkBox.setHorizontalAlignment(JCheckBox.CENTER);
                tableColumn.setCellEditor(new DefaultCellEditor(checkBox));
            }
            tableColumn.setWidth(column.getSize());
            tableColumn.setPreferredWidth(column.getSize());
            tableColumn.setResizable(true);

            ++columnIndex;
        }

        final JScrollPane scrollPane = new JScrollPane(result);
        final Dimension preferredSize = result.getPreferredScrollableViewportSize();
        if (model.getRowSize() > 0) {
            preferredSize.height = result.getRowHeight() * (model.getRowSize() + 1);
        }
        scrollPane.getViewport().setPreferredSize(preferredSize);

        componentCreated(model, scrollPane);

        return scrollPane;
    }

    protected final Component createSeparator(
            SeparatorModel model
    ) {
        if (model.isSpring()) {
            return Box.createGlue();
        } else {
            return Box.createHorizontalStrut(12);
        }
    }

    protected final Component createRuler(
            RulerModel model
    ) {
        final Ruler result = new Ruler(model.getAxis(), model.getBorder());

        final Dimension minSize = new Dimension(this.margin * 2, this.margin * 2);
        result.setMinimumSize(minSize);
        result.setPreferredSize(minSize);

        componentCreated(model, result);

        return result;
    }

    protected final Component createPlaceholder(
            PlaceholderModel model
    ) {
        String className = model.getClassName();
        if (!className.contains(".")) {
            // 占位符的类名中没有包含点(.)，说明类型名不是全名，需要补全。
            final String prefix;
            if (className.startsWith("J")) {
                // 如果类型名以字符J开头，那么看作Swing标准组件，需要加上Swing包名。
                prefix = "javax.swing.";
            } else {
                // 否则看作armory组件，需要加上armory的包名。
                prefix = "org.xx.armory.swing.components.";
            }
            className = prefix + className;
        }

        final JComponent result;
        try {
            result = (JComponent) buildDirectly(className, null);
        } catch (ReflectiveOperationException ex) {
            throw new IllegalArgumentException("cannot build component from placeholder[class-name=" + model.getClassName() + "]", ex);
        }

        if (model.isScrollable()) {
            final JScrollPane scrollPane = new JScrollPane(result);
            scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

            componentCreated(model, scrollPane);

            return scrollPane;
        } else {
            componentCreated(model, result);

            return result;
        }
    }

    protected final void createLabelsFor() {
        for (final Map.Entry<JLabel, String> entry : this.labelsFor.entrySet()) {
            final JLabel label = entry.getKey();
            final Component component = this.components.get(entry.getValue());
            if (label == null || component == null) {
                continue;
            }

            label.setLabelFor(component);
        }
    }
}
